package com.inetools.gwt.gajaxs.client;

import java.util.ArrayList;

import com.google.gwt.http.client.URL;

/**
 * Obtain events information from Google Calendar. 
 * Viewer for obtained events needs to implement GCalevenderLiteListener interface.
 * @author ay
 *
 */
public class GCalevenderLite {
	
	// For input data -----------------------------------------------------------------------------
	protected GCalenderDataApiInputOptions gCalenderDataApiInputOptions = null;
	public GCalenderDataApiInputOptions getGCalenderDataApiInputOptions() {
		return gCalenderDataApiInputOptions;
	}
	public void setGCalenderDataApiInputOptions(
			GCalenderDataApiInputOptions gCalenderDataApiInputOptions) {
		this.gCalenderDataApiInputOptions = gCalenderDataApiInputOptions;
	}
	// --------------------------------------------------------------------------------------------

	protected String gcDataFeedUrl = "http://www.google.com/calendar/feeds";
	
	public GCalevenderLite( ) {
	}
	
	// Interface what viewer of GCalevenderLite class needs to implement
	public interface GCalevenderLiteListener {
		public void onCallback( GCalenderFeedData gCalenderFeedData);
		public void onFailure();
	}
	
	protected ArrayList<GCalevenderLiteListener> gCalevenderLiteListenerArrayList = 
		new ArrayList<GCalevenderLiteListener>();
	/**
	 * Register gCalevenderLiteListener in order for its onCallback to be executed 
	 * once response is sent back from Google Calendar data api
	 * @param gCalevenderLiteListener
	 * @return true when registration succeed. 
	 * false when gCalevenderLiteListener has been already registered or it's null. 
	 */
	public boolean addGCalevenderLiteListener( GCalevenderLiteListener gCalevenderLiteListener) {
		if ( gCalevenderLiteListener == null) return false;
		if ( gCalevenderLiteListenerArrayList.contains( gCalevenderLiteListener)) {
			return false;
		}
		return gCalevenderLiteListenerArrayList.add( gCalevenderLiteListener);
	}
	public boolean removeGCalevenderLiteListener( GCalevenderLiteListener gCalevenderLiteListener) {
		if ( gCalevenderLiteListener == null) return false;
		return gCalevenderLiteListenerArrayList.remove( gCalevenderLiteListener);
	}
	
	protected void fireFeedDataReceivedEvent( GCalenderFeedData gCalenderFeedData) {
		for( GCalevenderLiteListener gCalevenderLiteListener : gCalevenderLiteListenerArrayList) {
			gCalevenderLiteListener.onCallback( gCalenderFeedData);
		} // for
	}
	protected void fireFailureEvent() {
		for( GCalevenderLiteListener gCalevenderLiteListener : gCalevenderLiteListenerArrayList) {
			gCalevenderLiteListener.onFailure();
		} // for
	}
	
	// Holder for callback ID for JSONP
	protected int jsonRequestId = 0;
	// Holder for time stamp of this object incidence as signature for multi instance support
	protected String callbackTimeStamp = null;
	// To set the number of maximum times of attempt of getting feed data from Google Calendar data API
	protected int maxCallbackAttemptCount = 2;
	/**
	 * @return the number of maximum times of attempt of getting feed data from Google Calendar data API
	 */
	public int getMaxCallbackAttemptCount() {
		return maxCallbackAttemptCount;
	}
	/**
	 * Set the number of maximum times of attempt of getting feed data from Google Calendar data API
	 * @param maxCallbackAttemptCount
	 */
	public void setMaxCallbackAttemptCount(int maxCallbackAttemptCount) {
		this.maxCallbackAttemptCount = maxCallbackAttemptCount;
	}
	
	protected int requestTimeOut = 2000;
	/**
	 * @return interval in milliseconds until next attempts after failure of getting feed data from Google Calendar data API
	 */
	public int getRequestTimeOut() {
		return requestTimeOut;
	}
	/**
	 * Set the interval in milliseconds until next attempts after failure of getting feed data from Google Calendar data API
	 * @param requestTimeOut
	 */
	public void setRequestTimeOut(int requestTimeOut) {
		this.requestTimeOut = requestTimeOut;
	}
	
	protected int jsTimeoutObjReferrer = -1;
	
	// TODO Make possible to specify the number of times of sending request to server to counter measure communication failure
	/**
	 * Construct callback function name by append requestId, and send request to make callback happen
	 * @param requestId
	 * @param url
	 * @param handlerInstance
	 */
	protected native void sendRequest( int requestId, String url, GCalevenderLite handlerInstance) /*-{
		// Construct id of call back insidence into callbackBaseName variable ---------------------
		var callbackBaseName = "gCalevenderLiteCallback";
		var callbackTimeStamp = 
			handlerInstance.@com.inetools.gwt.gajaxs.client.GCalevenderLite::callbackTimeStamp;
		if ( callbackTimeStamp == null) {
			callbackTimeStamp = new Date().getTime().toString();
			handlerInstance.@com.inetools.gwt.gajaxs.client.GCalevenderLite::callbackTimeStamp = callbackTimeStamp;
		}
		callbackBaseName = callbackBaseName + callbackTimeStamp + "_" + requestId;
		var callbackCountName = callbackBaseName + "Count"; 
		// ----------------------------------------------------------------------------------------
		
		var count = 0;
		if ( typeof ( window[ callbackCountName]) == "number") {
			// Clean up previous attempt residue --------------------------------------------------
			count = window[ callbackCountName];
			
			var lastCallbackName = callbackBaseName + "_" + count;
			var lastCallbackCompletionFlagName = lastCallbackName + "_CompleteOn";
			
			// Remove script DOM element and callback elements.
			var failedScriptNode = document.getElementById( lastCallbackName);
			if ( failedScriptNode) {
				document.body.removeChild( failedScriptNode);
			}
			delete window[ lastCallbackName];
			
			if ( window[ lastCallbackCompletionFlagName]) { // Callback method has been executed
				delete window[ lastCallbackCompletionFlagName];
				delete window[ callbackCountName];
				return;
			}
			else { // Callback method has not been called yet
				
				delete window[ lastCallbackCompletionFlagName];
				// Pass null to callback method
				// handlerInstance.@com.inetools.gwt.gajaxs.client.GCalevenderLite::fireFeedDataReceivedEvent(Lcom/inetools/gwt/gajaxs/client/GCalenderFeedData;)( null);
				
				// Give up when attempt exceeds maxCount ------------------------------------------
				var maxCount = handlerInstance.@com.inetools.gwt.gajaxs.client.GCalevenderLite::maxCallbackAttemptCount;
				if ( count >= maxCount) {
					delete window[ callbackCountName];
					// Notify failure to listeners
					handlerInstance.@com.inetools.gwt.gajaxs.client.GCalevenderLite::fireFailureEvent()();
					return;
				}
				// --------------------------------------------------------------------------------			
			}
			// ------------------------------------------------------------------------------------
		}

		window[ callbackCountName] = ++count;
		
		var callbackName = callbackBaseName + "_" + count;
		var callbackCompletionFlagName = callbackName + "_CompleteOn";
		
		// Define the callback function on the window object.
		// It receives the JSON data returned by the server as an argument.
		window[ callbackName] = 
			function( jsonObj) {
				// Hand over jsonObj to handler
				handlerInstance.@com.inetools.gwt.gajaxs.client.GCalevenderLite::fireFeedDataReceivedEvent(Lcom/inetools/gwt/gajaxs/client/GCalenderFeedData;)( jsonObj);
				// Flag to sign that the JSON callback was called.
				window[ callbackCompletionFlagName] = true;
			};
		
		// Create script DOM element to send request
		var script = document.createElement( "script");
		script.id = callbackName;
		script.setAttribute( "type", "text/javascript");
		// The src attribute points to the URL that will retrieve the JSON data wrapped in a callback function.
		script.setAttribute( "src", url + callbackName);
		
		// Send request by attaching the script element to the document body.
		document.body.appendChild( script);
		
		// Set timeout (default 2 seconds) to JSON download.
		// A timeout function is defined to check for an unresponsive server or network problem;
		// it checks a flag to see if the JSON callback was ever called.
		handlerInstance.@com.inetools.gwt.gajaxs.client.GCalevenderLite::jsTimeoutObjReferrer = 
			setTimeout(
				function() {
					handlerInstance.@com.inetools.gwt.gajaxs.client.GCalevenderLite::sendRequest(ILjava/lang/String;Lcom/inetools/gwt/gajaxs/client/GCalevenderLite;)( requestId, url, handlerInstance);
				}, 
				handlerInstance.@com.inetools.gwt.gajaxs.client.GCalevenderLite::requestTimeOut
				);
	}-*/;
	
	/**
	 * Prerequisite: gCalenderDataApiInputOptions needs to have been initialized by 
	 * setGCalenderDataApiInputOptions method
	 * @throws RuntimeException, IllegalArgumentException
	 */
	public void requestEventData() throws RuntimeException, IllegalArgumentException {
		if ( gCalenderDataApiInputOptions == null) {
			throw new RuntimeException( 
					"gCalenderDataApiInputOptions member field has not either initialized yet or set to null.");
		}

		if ( gCalevenderLiteListenerArrayList.size() < 1) return; // No point to execute when none is interested
		
		// On Google Calendar specific query parameters -------------------------------------------
		
		// Just sanity check on userId since gCalenderDataApiInputOptions should have 
		// performed validity check on it 
		if ( gCalenderDataApiInputOptions.getUserId() == null) {
			throw new IllegalArgumentException( 
				"userId cannot be neither null nor empty string as valid e-mail address format.");
		}
		
		// Add userId to gcDataFeedUrl
		gcDataFeedUrl = gcDataFeedUrl + "/" + gCalenderDataApiInputOptions.getUserId();
		
		// Add visibility to gcDataFeedUrl
		gcDataFeedUrl = gcDataFeedUrl + "/" + gCalenderDataApiInputOptions.getVisibility();
		
		// Add projection to gcDataFeedUrl
		gcDataFeedUrl = gcDataFeedUrl + "/" + gCalenderDataApiInputOptions.getProjection();

		// Specify return data format to json-in-script
		gcDataFeedUrl = gcDataFeedUrl + "?alt=json-in-script";
		
		// Add ctz to gcDataFeedUrl ---------------------------------------------------------------
		if ( gCalenderDataApiInputOptions.ctz != null) {
			String ctzCopy = gCalenderDataApiInputOptions.ctz.trim();
			if ( !( "".equals( ctzCopy))) {
				ctzCopy = ctzCopy.replace( " ", "_");
				
				gcDataFeedUrl = gcDataFeedUrl + "&ctz=" + ctzCopy;
			}
		}
		// ----------------------------------------------------------------------------------------
		
		// Add futureEvents to gcDataFeedUrl 
		gcDataFeedUrl = gcDataFeedUrl + "&futureevents=" + gCalenderDataApiInputOptions.isFutureEvents();
		
		// Add orderby to gcDataFeedUrl 
		gcDataFeedUrl = gcDataFeedUrl + "&orderby=" + gCalenderDataApiInputOptions.getOrderBy();
		
		// Add recurrence-expansion-start to gcDataFeedUrl 
		String recurrenceExpansionStartCopy = 
			gCalenderDataApiInputOptions.getRecurrenceExpansionStart();
		if ( recurrenceExpansionStartCopy != null) {
			gcDataFeedUrl = 
				gcDataFeedUrl + "&recurrence-expansion-start=" + recurrenceExpansionStartCopy;
		}
		
		// Add recurrence-expansion-end to gcDataFeedUrl 
		String recurrenceExpansionEndCopy = 
			gCalenderDataApiInputOptions.getRecurrenceExpansionEnd();
		if ( recurrenceExpansionEndCopy != null) {
			gcDataFeedUrl = 
				gcDataFeedUrl + "&recurrence-expansion-end=" + recurrenceExpansionEndCopy;
		}
		
		// Add singleevents to gcDataFeedUrl 
		gcDataFeedUrl = 
			gcDataFeedUrl + "&singleevents=" + gCalenderDataApiInputOptions.isSingleEvents();
		
		// Add showhidden to gcDataFeedUrl 
		gcDataFeedUrl = 
			gcDataFeedUrl + "&showhidden=" + gCalenderDataApiInputOptions.isShowHidden();
		
		// Add sortorder to gcDataFeedUrl 
		gcDataFeedUrl = gcDataFeedUrl + "&sortorder=" + gCalenderDataApiInputOptions.getSortOrder();
		
		// Add start-min to gcDataFeedUrl 
		String startMinCopy = gCalenderDataApiInputOptions.getStartMin();
		if ( startMinCopy != null) {
			gcDataFeedUrl = 
				gcDataFeedUrl + "&start-min=" + startMinCopy;
		}
		
		// Add start-max to gcDataFeedUrl 
		String startMaxCopy = gCalenderDataApiInputOptions.getStartMax();
		if ( startMaxCopy != null) {
			gcDataFeedUrl = 
				gcDataFeedUrl + "&start-max=" + startMaxCopy;
		}
		
		// Google data query parameters -----------------------------------------------------------
		// Add q to gcDataFeedUrl -----------------------------------------------------------------
		if ( gCalenderDataApiInputOptions.q != null) {
			String qCopy = gCalenderDataApiInputOptions.q.trim();
			if ( qCopy.length() > 0) {
				gcDataFeedUrl = gcDataFeedUrl + "&q=" + qCopy;
			}
		}
		// ----------------------------------------------------------------------------------------
		
		// Add author to gcDataFeedUrl -----------------------------------------------------------------
		if ( gCalenderDataApiInputOptions.author != null) {
			String authorCopy = gCalenderDataApiInputOptions.author.trim();
			if ( authorCopy.length() > 0) {
				gcDataFeedUrl = gcDataFeedUrl + "&author=" + authorCopy;
			}
		}
		// ----------------------------------------------------------------------------------------
		
		// Add updated-min to gcDataFeedUrl 
		String updatedMinCopy = gCalenderDataApiInputOptions.getUpdatedMin();
		if ( updatedMinCopy != null) {
			gcDataFeedUrl = 
				gcDataFeedUrl + "&updated-min=" + updatedMinCopy;
		}
		
		// Add updated-max to gcDataFeedUrl 
		String updatedMaxCopy = gCalenderDataApiInputOptions.getUpdatedMax();
		if ( updatedMaxCopy != null) {
			gcDataFeedUrl = 
				gcDataFeedUrl + "&updated-max=" + updatedMaxCopy;
		}
		
		// Add published-min to gcDataFeedUrl 
		String publishedMinCopy = gCalenderDataApiInputOptions.getPublishedMin();
		if ( publishedMinCopy != null) {
			gcDataFeedUrl = 
				gcDataFeedUrl + "&published-min=" + publishedMinCopy;
		}
		
		// Add published-max to gcDataFeedUrl 
		String publishedMaxCopy = gCalenderDataApiInputOptions.getPublishedMax();
		if ( publishedMaxCopy != null) {
			gcDataFeedUrl = 
				gcDataFeedUrl + "&published-max=" + publishedMaxCopy;
		}

		// Add start-index to gcDataFeedUrl 
		gcDataFeedUrl = 
			gcDataFeedUrl + "&start-index=" + gCalenderDataApiInputOptions.getStartIndex();
		
		// Add max-results to gcDataFeedUrl 
		gcDataFeedUrl = 
			gcDataFeedUrl + "&max-results=" + gCalenderDataApiInputOptions.getMaxResults();
		
		// ----------------------------------------------------------------------------------------
		
		// Send request ---------------------------------------------------------------------------
		gcDataFeedUrl = gcDataFeedUrl + "&callback="; 
		gcDataFeedUrl = URL.encode( gcDataFeedUrl);
		sendRequest( jsonRequestId++, gcDataFeedUrl, this);
		// ----------------------------------------------------------------------------------------
	}
	
}
